Skip to content

fix(adapters): api_url custom-endpoint config (Slack/Discord/GitHub/Linear) + GitHub public get_installation_id#150

Merged
patrick-chinchill merged 2 commits into
mainfrom
fix/adapter-api-url-and-github-installation
Jun 19, 2026
Merged

fix(adapters): api_url custom-endpoint config (Slack/Discord/GitHub/Linear) + GitHub public get_installation_id#150
patrick-chinchill merged 2 commits into
mainfrom
fix/adapter-api-url-and-github-installation

Conversation

@patrick-chinchill

Copy link
Copy Markdown
Collaborator

Part of the 0.4.30 pre-ship gap-port wave (apiurl-github group). Pre-existing parity gaps the upstream-parity audit found — all ported faithfully from upstream chat@4.30.0. Not regressions.

(A) api_url custom-endpoint config — upstream 4.27.0 (6b17c60)

Upstream added an apiUrl config (+ <ADAPTER>_API_URL env fallback) to every high-level adapter, routing a custom base URL into the underlying API client (proxies, API mocks, Enterprise / self-host endpoints). Four adapters were missing it:

Adapter Upstream ref Field Env Plumbed into
Slack index.ts:617,576-577,620-621 api_url SLACK_API_URL base_url on BOTH the async AsyncWebClient cache and the sync web_client escape hatch
Discord types.ts:18-19, index.ts:142 api_url DISCORD_API_URL _discord_fetch (single HTTP funnel)
GitHub index.ts:201,217 api_url GITHUB_API_URL _github_api_request + installation-token exchange URL
Linear index.ts:221,239,249, types.ts:51 api_url LINEAR_API_URL _graphql_query GraphQL endpoint

For each: config wins over env, env over default, default unchanged when unset. is not None honors an explicit empty string (CLAUDE.md truthiness-trap hazard). Slack omits base_url entirely when unset (slack_sdk rejects base_url=None and keeps its built-in default).

(B) GitHub public get_installation_id — index.ts:458-480

Added the public get_installation_id(thread | str) returning the fixed installation id (single-tenant app mode), the cached repo installation (multi-tenant), or None (PAT mode); raises ValidationError when multi-tenant and uninitialized. The private _get_installation_id(owner, repo) helper is retained (delegated to in multi-tenant mode).

Tests

tests/test_adapter_api_url_config.py — 31 tests:

  • Per adapter: custom-url-actually-used, default-unchanged-when-unset, env-fallback, config-wins-over-env (Slack additionally asserts no base_url is passed when unset, plus an end-to-end check against the real slack_sdk normalizer).
  • GitHub get_installation_id: all six auth-mode branches (PAT→None, single-tenant→fixed, multi-tenant cached→id, Thread-object input, multi-tenant uncached→None, multi-tenant uninitialized→raises).

The usage-site assertions were verified to fail against the pre-fix sources (reverting each plumb made exactly the 8 custom-url usage tests fail).

Gauntlet (from worktree)

  • ruff check — clean
  • ruff format --check — clean
  • audit_test_quality.py — 0 hard failures
  • pyrefly check — 0 errors
  • pytest — 4804 passed, 3 skipped (baseline 4773; +31 from the new file)

Notes

  • Slack SocketModeClient (WebSocket event transport) is intentionally left unrouted — upstream's slackApiUrl only configures the Web API clients, not the socket connection.
  • GitHub api_url strips a trailing slash so f"{base}{path}" joins cleanly with leading-slash paths (Enterprise endpoints often carry one). Linear's OAuth token endpoint (LINEAR_TOKEN_URL) is intentionally not overridden — upstream's apiUrl only targets the GraphQL apiUrl.
  • Did NOT touch CHANGELOG.md / docs/UPSTREAM_SYNC.md (release docs consolidated separately) or the Teams adapter (handled separately).

…inear) + GitHub public get_installation_id

Pre-existing parity gaps ported faithfully from upstream chat@4.30.0.

(A) api_url custom-endpoint config (upstream 4.27.0, 6b17c60) — every
high-level adapter reads `config.apiUrl ?? <ADAPTER>_API_URL` env and
routes it into the underlying API client. Add the field (+ env fallback,
default unchanged when unset) to the four adapters that were missing it:

- Slack (index.ts:617,576-577,620-621): new `api_url` (+ SLACK_API_URL).
  Passed as `base_url` to BOTH the async AsyncWebClient cache and the
  synchronous web_client escape hatch. Omitted entirely when unset
  (slack_sdk rejects base_url=None and keeps its built-in default).
- Discord (types.ts:18-19, index.ts:142): new `api_url` (+ DISCORD_API_URL)
  replacing the hardcoded DISCORD_API_BASE in `_discord_fetch` — the single
  funnel all Discord HTTP goes through.
- GitHub (index.ts:201,217): new `api_url` (+ GITHUB_API_URL) for GitHub
  Enterprise Server. Threaded into `_github_api_request` and the
  installation-token exchange URL (the two hardcoded api.github.com sites).
- Linear (index.ts:221,239,249, types.ts:51): new `api_url`
  (+ LINEAR_API_URL) overriding the GraphQL endpoint in `_graphql_query`.

(B) GitHub public get_installation_id (index.ts:458-480) — add the public
`get_installation_id(thread|str)` returning the fixed id (single-tenant
app), the cached repo installation (multi-tenant), or None (PAT mode);
raises when multi-tenant and uninitialized. Keeps the private
`_get_installation_id(owner, repo)` helper it delegates to.

Tests: tests/test_adapter_api_url_config.py (31) — per-adapter custom-url
used / default-unchanged / env-fallback / config-wins-over-env, plus the
six get_installation_id auth-mode branches. The usage-site assertions were
verified to fail against the pre-fix sources.
@coderabbitai

coderabbitai Bot commented Jun 18, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@patrick-chinchill, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 53 minutes and 9 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 63df506c-f3b6-40a8-b8b7-648326d181e7

📥 Commits

Reviewing files that changed from the base of the PR and between 17aa41a and 4dc7d38.

📒 Files selected for processing (9)
  • src/chat_sdk/adapters/discord/adapter.py
  • src/chat_sdk/adapters/discord/types.py
  • src/chat_sdk/adapters/github/adapter.py
  • src/chat_sdk/adapters/github/types.py
  • src/chat_sdk/adapters/linear/adapter.py
  • src/chat_sdk/adapters/linear/types.py
  • src/chat_sdk/adapters/slack/adapter.py
  • src/chat_sdk/adapters/slack/types.py
  • tests/test_adapter_api_url_config.py

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request adds custom api_url configuration options and environment variable fallbacks across the Discord, GitHub, Linear, and Slack adapters to support custom API base URLs. It also implements a public get_installation_id method on the GitHub adapter and includes comprehensive tests. One issue was identified in the GitHub adapter where a truthiness check on api_url_raw fails to honor an explicit empty string, and should be replaced with an is not None check.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

# explicit empty string (CLAUDE.md truthiness-trap hazard).
config_api_url = config.get("api_url")
api_url_raw = config_api_url if config_api_url is not None else os.environ.get("GITHUB_API_URL")
self._api_url = api_url_raw.rstrip("/") if api_url_raw else GITHUB_API_BASE_URL

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The truthiness check if api_url_raw will evaluate to False if api_url_raw is an explicit empty string (""), causing it to incorrectly fall back to GITHUB_API_BASE_URL. This violates the general rule of using is not None for optional values that can be falsy but valid, and contradicts the comment's stated intent to honor explicit empty strings.

Please use is not None instead of a truthiness check.

Suggested change
self._api_url = api_url_raw.rstrip("/") if api_url_raw else GITHUB_API_BASE_URL
self._api_url = api_url_raw.rstrip("/") if api_url_raw is not None else GITHUB_API_BASE_URL
References
  1. When checking for optional values that can be falsy but valid (e.g., 0, empty string, empty list), use is not None instead of a truthiness check to avoid silently ignoring them.

…efault endpoint

The api_url config test module guarded every test behind a module-level
pytest.importorskip("slack_sdk"). Since slack_sdk lives only in the optional
slack/all extras (not the dev group CI installs), the whole file collected as
a single skip -- silently disabling the Discord/GitHub/Linear api_url tests and
the get_installation_id regression tests in CI (falsely green).

Stub slack_sdk into sys.modules (mirroring tests/test_slack_client_cache.py) so
the module imports without the real package and the Slack tests run too. The
file now reports 34 passed, 1 skipped (the lone skip is the genuine-sdk
end-to-end test, intentionally skipped when stubbed) instead of 1 skipped.

Empty-string api_url now falls back to the default endpoint, matching upstream's
truthy spread ...(this.apiUrl ? {...} : {}):
- Slack: empty apiUrl/env resolves to None, so base_url is omitted and slack_sdk
  keeps https://slack.com/api/ (never passes base_url="").
- Linear: empty resolves to LINEAR_API_URL instead of POSTing to an empty URL.
- GitHub: empty resolves to https://api.github.com; fixed the misleading inline
  comment that claimed the is-not-None check honored empty string.
- Discord: empty resolves to DISCORD_API_BASE instead of a relative request URL.

Added a per-adapter empty-string regression test (Slack/Discord/GitHub/Linear).
@patrick-chinchill patrick-chinchill marked this pull request as ready for review June 18, 2026 23:59
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@patrick-chinchill patrick-chinchill merged commit b4ea70d into main Jun 19, 2026
9 checks passed
patrick-chinchill added a commit that referenced this pull request Jun 19, 2026
CHANGELOG: add 'Pre-existing parity gaps closed (4.30 audit)' subsection
covering the 7 ported fixes (PRs #147-#150) and a documented-exceptions
note (Linear agent-sessions #151 deferred to 4.31 / #152; adapter-web and
the GitHub/Linear native-client + message.subject halves stay Known
Non-Parity).

UPSTREAM_SYNC:
- file-mapping table: add state-ioredis -> redis.py (IoRedisStateAdapter)
  and state-pg -> postgres.py rows (both ported + tested, were missing).
- Known Non-Parity (platform gaps): add Linear agent-sessions row (#151)
  and an adapter-web row that splits the portable server-side WebAdapter
  (deferred) from the genuinely browser-only client subpaths, correcting
  the earlier over-broad 'browser-only; no Python runtime' note.
- Correct the Google Chat file-uploads row: inbound attachment parsing is
  fully implemented (_create_attachment), so the gap is outbound file
  delivery only (post_message still logs 'not yet supported').
patrick-chinchill added a commit that referenced this pull request Jun 19, 2026
…PR 4/4) (#146)

* chore(release): cut 0.4.30 — Teams SDK migration + 4.30.0 parity (#93 PR 4/4)

Final PR of the Teams SDK migration (issue #93). PRs 1-3 (inbound+auth,
outbound, native streaming) are merged; this cuts the 0.4.30 release.

- Version bump: pyproject 0.4.29 -> 0.4.30; UPSTREAM_PARITY "4.29.0" -> "4.30.0".
- Fidelity re-pin chat@4.29.0 -> chat@4.30.0 in lint.yml +
  verify_test_fidelity.py (docstring, default parity fallback, clone hint).
  packages/chat/src is byte-for-byte identical between the two tags, so zero
  new test ports: strict fidelity stays 100% (732/732, 0 missing) against
  chat@4.30.0. Baseline regenerated (ts_parity -> chat@4.30.0; the recorded
  total_ts_tests literal 731 -> 732 corrects a stale count from the merged
  adapter waves, not the re-pin — the count is identical against both tags).
- Docs: project-instructions version map + fidelity pin; README status line;
  CHANGELOG 0.4.30 entry (Twilio adapter, Telegram streaming, Slack subpaths,
  WhatsApp/Slack/gchat fixes, Teams #93 PRs 1-4); UPSTREAM_SYNC.md parity
  header + the Teams deferral row flipped to delivered.
- Version-label normalization: malformed `adapter-teams@chat@4.30.0` and loose
  `adapter-teams@4.30.0` -> `@chat-adapter/teams@4.30.0` in adapter.py (5),
  bridge.py (1), UPSTREAM_SYNC.md (4). Comment/doc-only.

Does NOT tag/publish — the release is a separate maintainer-gated step (live
Teams 429 streaming check + PyPI authorization).

* docs(release): clarify 0.4.30 core-parity wording + finish label normalization

Review fast-follow for PR 4. The CHANGELOG now states the mapped core is
content-identical *between the chat@4.29.0 and chat@4.30.0 upstream tags*
(verified: thread.ts/types.ts/thread.test.ts and the full packages/chat/src
tree are byte-identical) — the prior 'unchanged from 4.29.0' phrasing was
accurate but misread as a claim about our code. Also sweeps the two
remaining old-style `adapter-teams@chat@4.30.0` labels in the Teams test
docstrings to the canonical `@chat-adapter/teams@4.30.0` npm tag,
completing the normalization the PR's scope called for. No logic change.

* docs(release): document 0.4.30 parity-audit wave + remaining exceptions

CHANGELOG: add 'Pre-existing parity gaps closed (4.30 audit)' subsection
covering the 7 ported fixes (PRs #147-#150) and a documented-exceptions
note (Linear agent-sessions #151 deferred to 4.31 / #152; adapter-web and
the GitHub/Linear native-client + message.subject halves stay Known
Non-Parity).

UPSTREAM_SYNC:
- file-mapping table: add state-ioredis -> redis.py (IoRedisStateAdapter)
  and state-pg -> postgres.py rows (both ported + tested, were missing).
- Known Non-Parity (platform gaps): add Linear agent-sessions row (#151)
  and an adapter-web row that splits the portable server-side WebAdapter
  (deferred) from the genuinely browser-only client subpaths, correcting
  the earlier over-broad 'browser-only; no Python runtime' note.
- Correct the Google Chat file-uploads row: inbound attachment parsing is
  fully implemented (_create_attachment), so the gap is outbound file
  delivery only (post_message still logs 'not yet supported').

* docs(release): correct phantom-feature framing before PyPI cut

CHANGELOG:
- Drop false gchat file-delivery bullet (PR #112 never merged; upstream
  itself does not implement gchat outbound file delivery)
- Fix Slack DM block-action citation to merged PR #137 (supersedes #133)
- Re-point 0.4.27.1 vehicle from unmerged #120 to tag v0.4.27.1 + #117
- Drop numberless Twilio 'scaffolding PR' (only #142 exists)

UPSTREAM_SYNC:
- Reframe Google Chat outbound file delivery as parity (upstream also
  logs 'not yet supported' + media.upload TODO, index.ts:1282-1289)
- Correct Teams cert-auth row: TS is not 'Supported' — config throws at
  startup in both SDKs (types.ts:31, config.ts:13); parity, issue #58
- Add Messenger get_user row: upstream has no getUser; raising stub is
  parity, Graph-API impl tracked as #132

* docs(changelog): clarify 0.4.30 audit-gap count (8 closed + 1 deferred = 9)
@patrick-chinchill patrick-chinchill deleted the fix/adapter-api-url-and-github-installation branch June 19, 2026 09:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant